/**
 * File: ProcessMaq.java
 * Created by: mhaimel
 * Created on: 2 Apr 2009
 * CVS:  $Id: ProcessMaq.java,v 1.2 2009/06/19 13:02:48 mhaimel Exp $
 */
package uk.ac.ebi.velvet.processor.impl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.VelvetUncheckedException;
import uk.ac.ebi.velvet.config.VelvetArgs;
import uk.ac.ebi.velvet.config.VelvetConfig;
import uk.ac.ebi.velvet.exec.ProcessorService;
import uk.ac.ebi.velvet.exec.impl.MaqContigExec;
import uk.ac.ebi.velvet.exec.impl.MaqMatchExec;
import uk.ac.ebi.velvet.util.config.ConfigFactory;

/**
 * @author mhaimel
 *
 */
public class ProcessMaq {
	private static final String MAQ_PARALLEL_CATEGORY = "MAQ-PARALLEL";
	private Log log = LogFactory.getLog(this.getClass());
	private VelvetConfig config;
	private File baseDir;
	private List<FileInfo> readFiles = new ArrayList<FileInfo>();
	private List<FileInfo> contigFiles = new ArrayList<FileInfo>();
	private ProcessorService processorService;
	private VelvetArgs velvetArgs;
	
	public ProcessMaq(File baseDir, VelvetArgs velvetArgs) {
		this.baseDir = baseDir;
		this.velvetArgs = velvetArgs;
	}
	
	public Collection<FileInfo> runAllMaq(){
		getLog().info("Match reads using maq ...");
		File ctgBfa = tranformContigFile(getContigFile());
		getLog().info("Transform and match read files: " + readFiles.size());
		List<Future<Collection<FileInfo>>> futurama = new ArrayList<Future<Collection<FileInfo>>>();
		// separate Runners for fastq2bfq and match +matchview
		for(FileInfo info : readFiles){
			MaqMatchExec job = new MaqMatchExec(getBaseDir(),ctgBfa,info,getVelvetArgs().getInsertLength(info));
			job.setProcessorService(getProcessorService());
			Future<Collection<FileInfo>> res = submit(job);
			futurama.add(res);
		}
		List<FileInfo> resList = new ArrayList<FileInfo>();
		try {
			for(Future<Collection<FileInfo>> future : futurama){
				resList.addAll(future.get());
			}
		} catch (InterruptedException e) {
			throw new VelvetUncheckedException(e);
		} catch (ExecutionException e) {
			throw new VelvetUncheckedException(e);			
		} finally{
			shutdownQueue(true);
		}
		return resList;
	}	

	private File tranformContigFile(FileInfo fileInfo) {
		getLog().info("Transform contig to bfa ...");
		File bfa = new File(getBaseDir(),fileInfo.getFile().getName()+".bfa");
		MaqContigExec ctgExec = new MaqContigExec(getBaseDir());
		ctgExec.setProcessorService(getProcessorService());
		ctgExec.runMaqFasta2Bfa(fileInfo, bfa);
		return bfa;
	}

	/**
	 * If more than one contig file is provided, merging is necessary
	 * @return
	 */
	private FileInfo getContigFile() {
		FileInfo ctgFile = null;
		if(getContigFileColl().size() > 1){
			ctgFile = new FileInfo(new File(getBaseDir(),"contig.fa"),FileType.fasta,ReadType.Contig);
			merge(ctgFile,getContigFileColl());
		} else if (getContigFileColl().isEmpty()){
			throw new InvalidParameterException(
					"No Contig File provided!!!");
		} else {
			ctgFile = getContigFileColl().get(0);
		}
		return ctgFile;
	}

	private void merge(FileInfo ctgFile, List<FileInfo> files) {
		BufferedWriter out = null;
		try {
			out = new FileIO(ctgFile.getFile()).getBufferedWriter();
			for(FileInfo f : files){
				if(f.getFileType().equals(ctgFile.getFileType())){
					BufferedReader in = null;
					try {
						in = new FileIO(f.getFile()).getBufferedReader();
						IOUtils.copy(in, out);
						out.newLine();
					} catch (IOException e) {
						throw new IOException(
								"Problems reading from " + f,e);
					}finally{
						IOUtils.closeQuietly(in);
					}
				} else {
					throw new NotImplementedException(
							"Merge Contig files of differnt type has not happend yet");
				}
			}
		} catch (IOException e1) {
			throw new VelvetUncheckedException(
					"Problems writing to " + ctgFile,e1);
		} finally{
			IOUtils.closeQuietly(out);
		}
	}

	private VelvetConfig getConfig() {
		if(null == config){
			config = ConfigFactory.create(VelvetConfig.class);
		}
		return config;
	}

	public void addReadFiles(Collection<FileInfo> files) {
		getReadFiles().addAll(files);
	}

	private List<FileInfo> getReadFiles() {
		return readFiles;
	}

	public void addContigFiles(List<FileInfo> files) {
		getContigFileColl().addAll(files);
	}
	private List<FileInfo> getContigFileColl() {
		return contigFiles;
	}
	private File getBaseDir() {
		return baseDir;
	}
	
	private Log getLog() {
		return log;
	}
	
	private VelvetArgs getVelvetArgs() {
		return velvetArgs;
	}

	public <V> Future<V> submit(Callable<V> job){
		return this.getProcessorService().process(MAQ_PARALLEL_CATEGORY, job);
	}
	
	private void shutdownQueue(boolean force){
		this.getProcessorService().shutdown(MAQ_PARALLEL_CATEGORY,force);
	}
	
	public void setProcessorService(ProcessorService processorService) {
		this.processorService = processorService;
		this.processorService.registerCategory(MAQ_PARALLEL_CATEGORY, getConfig().getMaqParallel());
	}	
	private ProcessorService getProcessorService() {
		return processorService;
	}
}
